/*-------------------------------------------------------------------------
 *
 * time_mapping.c
 *	  时间到 XID 的映射信息
 *
 * Copyright (c) 2020-2022, PostgreSQL Global Development Group
 *
 *	  contrib/old_snapshot/time_mapping.c
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "funcapi.h"
#include "storage/lwlock.h"
#include "utils/old_snapshot.h"
#include "utils/snapmgr.h"
#include "utils/timestamp.h"

/*
 * 后端私有的与旧快照控制相关的信息副本，涉及时间到 XID 的映射，以及一个索引，以便我们能够进行迭代。
 *
 * 请注意，xid_by_minute 数组的长度由 OLD_SNAPSHOT_TIME_MAP_ENTRIES 给出（这不是一个编译时常量）。
 */
typedef struct
{
	int			current_index;
	int			head_offset;
	TimestampTz head_timestamp;
	int			count_used;
	TransactionId xid_by_minute[FLEXIBLE_ARRAY_MEMBER];
} OldSnapshotTimeMapping;

#define NUM_TIME_MAPPING_COLUMNS 3

PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(pg_old_snapshot_time_mapping);

static OldSnapshotTimeMapping *fc_GetOldSnapshotTimeMapping(void);
static TupleDesc fc_MakeOldSnapshotTimeMappingTupleDesc(void);
static HeapTuple fc_MakeOldSnapshotTimeMappingTuple(TupleDesc fc_tupdesc,
												 OldSnapshotTimeMapping *fc_mapping);

/*
 * 可通过 SQL 调用的返回集函数。
 */
Datum pg_old_snapshot_time_mapping(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	OldSnapshotTimeMapping *fc_mapping;

	if (SRF_IS_FIRSTCALL())
	{
		MemoryContext fc_oldcontext;

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);
		fc_mapping = fc_GetOldSnapshotTimeMapping();
		fc_funcctx->user_fctx = fc_mapping;
		fc_funcctx->tuple_desc = fc_MakeOldSnapshotTimeMappingTupleDesc();
		MemoryContextSwitchTo(fc_oldcontext);
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	fc_mapping = (OldSnapshotTimeMapping *) fc_funcctx->user_fctx;

	while (fc_mapping->current_index < fc_mapping->count_used)
	{
		HeapTuple	fc_tuple;

		fc_tuple = fc_MakeOldSnapshotTimeMappingTuple(fc_funcctx->tuple_desc, fc_mapping);
		++fc_mapping->current_index;
		SRF_RETURN_NEXT(fc_funcctx, HeapTupleGetDatum(fc_tuple));
	}

	SRF_RETURN_DONE(fc_funcctx);
}

/*
 * 从共享内存中获取旧快照时间映射数据。
 */
static OldSnapshotTimeMapping *
fc_GetOldSnapshotTimeMapping(void)
{
	OldSnapshotTimeMapping *fc_mapping;

	fc_mapping = palloc(offsetof(OldSnapshotTimeMapping, xid_by_minute)
					 + sizeof(TransactionId) * OLD_SNAPSHOT_TIME_MAP_ENTRIES);
	fc_mapping->current_index = 0;

	LWLockAcquire(OldSnapshotTimeMapLock, LW_SHARED);
	fc_mapping->head_offset = oldSnapshotControl->head_offset;
	fc_mapping->head_timestamp = oldSnapshotControl->head_timestamp;
	fc_mapping->count_used = oldSnapshotControl->count_used;
	for (int fc_i = 0; fc_i < OLD_SNAPSHOT_TIME_MAP_ENTRIES; ++fc_i)
		fc_mapping->xid_by_minute[fc_i] = oldSnapshotControl->xid_by_minute[fc_i];
	LWLockRelease(OldSnapshotTimeMapLock);

	return fc_mapping;
}

/*
 * 为 pg_old_snapshot_time_mapping() SRF 构建一个元组描述符。
 */
static TupleDesc fc_MakeOldSnapshotTimeMappingTupleDesc(void)
{
	TupleDesc	fc_tupdesc;

	fc_tupdesc = CreateTemplateTupleDesc(NUM_TIME_MAPPING_COLUMNS);

	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "array_offset",
					   INT4OID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "end_timestamp",
					   TIMESTAMPTZOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "newest_xmin",
					   XIDOID, -1, 0);

	return BlessTupleDesc(fc_tupdesc);
}

/*
 * 将旧快照时间映射中的一个条目转换为 HeapTuple。
 */
static HeapTuple fc_MakeOldSnapshotTimeMappingTuple(TupleDesc fc_tupdesc, OldSnapshotTimeMapping *fc_mapping)
{
	Datum		fc_values[NUM_TIME_MAPPING_COLUMNS];
	bool		fc_nulls[NUM_TIME_MAPPING_COLUMNS];
	int			fc_array_position;
	TimestampTz fc_timestamp;

	/*
	 * 确定与当前索引对应的数组位置。
	 *
	 * 索引 0 表示映射中的最旧条目，该条目存储在 mapping->head_offset 中。索引 1 表示下一个最旧条目，对应于下一个索引，以此类推。当我们到达数组末尾时，我们会循环回去。
	 */
	fc_array_position = (fc_mapping->head_offset + fc_mapping->current_index)
		% OLD_SNAPSHOT_TIME_MAP_ENTRIES;

	/*
	 * 除最旧的条目外，没有为任何条目存储显式时间戳，但每个条目对应一个 1 分钟的时间段，因此我们可以直接相加。
	 */
	fc_timestamp = TimestampTzPlusMilliseconds(fc_mapping->head_timestamp,
											fc_mapping->current_index * 60000);

	/* 初始化 nulls 和 values 数组。 */
	memset(fc_nulls, 0, sizeof(fc_nulls));
	fc_values[0] = Int32GetDatum(fc_array_position);
	fc_values[1] = TimestampTzGetDatum(fc_timestamp);
	fc_values[2] = TransactionIdGetDatum(fc_mapping->xid_by_minute[fc_array_position]);

	return heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);
}
